/**
 * AGNRotationalParameterHandle.java
 */

package agngui.agnmodel;

import javax.media.j3d.PickRay;
import javax.media.j3d.Transform3D;
import javax.vecmath.AxisAngle4d;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

/**
 * This class is for handles that are responsible for rotating along the three primary
 * axes.
 */
public class AGNRotationalParameterHandle extends AGNParameterHandle {
	
	/**
	 * The initial point at which this handle is located
	 */
    private Point3d initialHandlePoint = null;
    
    /**
     * The center point of this handle
     */
    private Point3d centerPoint        = null;
    
    /**
     * The radius of the circle this handle spins around
     */
    private double  radius             = 0.0;
    
    /**
     * The initial value of theta (angular location around the spin circle)
     */
    private double  initialValue       = 0.0;
    
    /**
     * AGNRotationalParameterHandle
     *
     * Initialize this rotational parameter object.
     *
     * @param object The object that this handle belongs to
     * @param point  The point (in world space) that the handle currently lies on. This point
     *               lies in the rotational plane about the control axis a distance [this.radius]
     *               units from the control axis. This point defines theta = 0 about the control axis.
     * @param axis   The rotational axis this parameter rotates about
     * @param startAngle The initial angle the rotational handle begins at in radians
     * @param identifier The parameter identifier 
     */
    public AGNRotationalParameterHandle( AGNObject object, Point3d point, Vector3d axis,
                                                           double startAngle, String identifier ) {
        super( AGNParameterHandle.TYPE_ROTATIONAL, object, point, axis, identifier );
        // Rotate the point to reflect the change in angle
        AxisAngle4d pointRotation  = new AxisAngle4d( this.axis, startAngle );
        Transform3D pointTransform = new Transform3D();

        pointTransform.setRotation( pointRotation );
        pointTransform.transform( point );

        initialHandlePoint = new Point3d( point );

        // Project initial handle point onto the control axis
        Vector3d initialHandleVector = new Vector3d( point );
        Vector3d controlAxisCopy     = new Vector3d( this.axis );
        Vector3d diff                = new Vector3d();
        double   projectionLength;

        controlAxisCopy.normalize();
        projectionLength = controlAxisCopy.dot( initialHandleVector );
        controlAxisCopy.scale( projectionLength );
        
        diff.sub( controlAxisCopy, initialHandleVector );

        centerPoint = new Point3d( controlAxisCopy );
        radius      = diff.length();
        
        initialValue = recentValue = startAngle;
    }

    /**
     * calculateNewValueFromWorldLocation
     *
     * Calculates the new handle value based on the world point passed, and
     * stores the most recent value internally.
     *
     * @param worldPoint The point in world space projected by
     *                   projectClickOntoControlAxis
     * @return The calculated value (depends on handle type)
     */
    public double calculateNewValueFromWorldLocation( Point3d worldPoint ) {
        Vector3d       delta                = new Vector3d();
        Vector3d       handleVector         = new Vector3d( worldPoint );
        double         cosTheta;
        double         theta;

        // Sanitize the vectors...
        delta.sub( initialHandlePoint, centerPoint );
        handleVector.sub( centerPoint );
        delta.normalize();
        handleVector.normalize();

        // Get the angle between them
        cosTheta = delta.dot( handleVector );
        theta    = Math.acos( cosTheta );

        // CW or CCW?
        delta.cross( delta, handleVector );
        if( delta.dot( new Vector3d( centerPoint ) ) < 0.0 ) {
            theta = -theta;
        }

        recentValue = initialValue + theta;
        //performing a mod by 2*PI on a double value.
        while(recentValue > (2*Math.PI)) { recentValue -= (2*Math.PI); }
        while(recentValue < 0) { recentValue += (2*Math.PI); }


        return recentValue;
    }    

    /**
     * projectClickOntoControlAxis
     *
     * Projects a mouse click onto the circular control "circle" in world space
     * and gets the world-space intersection point.
     *
     * @param mX The mouse click x-coordinate
     * @param mY The mouse click y-coordinate
     * @param renderer The model renderer, needed for camera access and projection
     * @return The point in world-space of the finally projected point
     */
    public Point3d projectClickOntoControlAxis( int mX, int mY, AGNModelRenderer renderer ) {
        PickRay     ray;

        Vector3d rayDir       = new Vector3d();
        Vector3d controlAxis  = new Vector3d( axis );
        Vector3d planeNormal  = new Vector3d();
        //Vector3d planeToPoint = new Vector3d();
		Vector3d lookAtVector = new Vector3d();
        Vector3d clickVector  = new Vector3d();
        Point3d  clickPoint   = new Point3d();
        Point3d  rayOrigin    = new Point3d();
        double   D;
        double   t;
        double   Vd;

        // The plane normal is the axis of rotation
        planeNormal.set( controlAxis );
        planeNormal.normalize(); 
		
		lookAtVector.sub(renderer.getCamera().getLocation(), renderer.getCamera().getLookAt());
		lookAtVector.normalize();

        // Get the point of the mouse click in world space via ray-plane intersection
        ray = renderer.getClickRay( mX, mY );
        ray.get( rayOrigin, rayDir );
        rayDir.normalize();
        Vd = planeNormal.dot( rayDir );

        D = -planeNormal.dot( new Vector3d( point ) );
        t = -(planeNormal.dot( new Vector3d( rayOrigin )) + D) / Vd;

        if( t < 0 ) {
        	// Plane is behind the camera? Shouldn't be possible
        	System.out.println( "Plane is behind the camera (rotational handle)" );
        }

        rayDir.scale( t );
        clickPoint.add( rayOrigin, rayDir );

        // Project the point onto the rotational circle
        clickVector.sub( clickPoint, centerPoint );
        clickVector.normalize();
        clickVector.scale( radius );
        clickVector.add( centerPoint );


        return new Point3d( clickVector );
    }

    /**
     * getCenterPoint
     *
     * Gets this handle's center point.
     *
     * @return This handle's center point.
     */
    public Point3d getCenterPoint() {
        return centerPoint;
    }

    /**
     * getRadius
     *
     * Gets this handle's radius.
     *
     * @return This handle's radius.
     */
    public double getRadius() {
        return radius;
    }

    /**
     * getInitialHandlePoint
     *
     * Gets this handle's initial point.
     *
     * @return This handle's initial point.
     */
    public Point3d getInitialHandlePoint() {
        return initialHandlePoint;
    }

    /**
     * getInitialValue
     *
     * Gets this handle's initial value.
     *
     * @return this handle's initial value.
     */
    public double getInitialValue() {
        return initialValue;
    }

}
